/**
 * @brief SPI-Flash 驱动
 * @author 张勇 / 2020-05-13
 */

#include "share/unios.h"
#include "share/utils.h"
#include "mcu.h"
#include "spiflash.h"

#include "share/log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "NAND"
#include "share/log4app.h"

/* 指令表 */
#define FLASH_WriteEnable           0x06 
#define FLASH_WriteDisable          0x04 
#define FLASH_ReadStatusReg1        0x05 
#define FLASH_ReadStatusReg2        0x35 
#define FLASH_ReadStatusReg3        0x15 
#define FLASH_WriteStatusReg1       0x01 
#define FLASH_WriteStatusReg2       0x31 
#define FLASH_WriteStatusReg3       0x11 
#define FLASH_ReadData              0x03 
#define FLASH_FastReadData          0x0B 
#define FLASH_FastReadDual          0x3B 
#define FLASH_FastReadQuad          0xEB  
#define FLASH_PageProgram           0x02 
#define FLASH_PageProgramQuad       0x32 
#define FLASH_BlockErase            0xD8 
#define FLASH_SectorErase           0x20 
#define FLASH_ChipErase             0xC7 
#define FLASH_PowerDown             0xB9 
#define FLASH_ReleasePowerDown      0xAB 
#define FLASH_DeviceID              0xAB 
#define FLASH_ManufactDeviceID      0x90 
#define FLASH_JedecDeviceID         0x9F 
#define FLASH_Enable4ByteAddr       0xB7
#define FLASH_Exit4ByteAddr         0xE9
#define FLASH_SetReadParam          0xC0 
#define FLASH_EnterQPIMode          0x38
#define FLASH_ExitQPIMode           0xFF

/**
 * @brief       25QXX写使能
 * @note        将S1寄存器的WEL置位
 */
static void unidrv_spiflash_write_enable(mcu_spiflash_t *dev) {
    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_WriteEnable);   // 发送写使能
    dev->cs(false);
}

/**
 * @brief       25QXX发送地址
 * @note        根据芯片型号的不同, 发送24ibt / 32bit地址
 * @param       address : 要发送的地址
 * @retval      无
 */
static void unidrv_spiflash_send_address(mcu_spiflash_t *dev, uint32_t address) {
    // 只有 25Q256 支持 4 字节地址模式
    if (dev->typeid == FTID_W25Q256 || dev->typeid == FTID_BY25Q256) {
        mcu_spi_trx1(dev->spi, (uint8_t)((address)>>24)); // 发送 bit31 ~ bit24 地址
    } 
    mcu_spi_trx1(dev->spi, (uint8_t)((address)>>16));     // 发送 bit23 ~ bit16 地址
    mcu_spi_trx1(dev->spi, (uint8_t)((address)>>8));      // 发送 bit15 ~ bit8  地址
    mcu_spi_trx1(dev->spi, (uint8_t)address);             // 发送 bit7  ~ bit0  地址
}

/**
 * @brief       读取25QXX的状态寄存器，25QXX一共有3个状态寄存器
 * @note        状态寄存器1：
 *              BIT7  6   5   4   3   2   1   0
 *              SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 *              SPR:默认0,状态寄存器保护位,配合WP使用
 *              TB,BP2,BP1,BP0:FLASH区域写保护设置
 *              WEL:写使能锁定
 *              BUSY:忙标记位(1,忙;0,空闲)
 *              默认:0x00
 *
 *              状态寄存器2：
 *              BIT7  6   5   4   3   2   1   0
 *              SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
 *
 *              状态寄存器3：
 *              BIT7      6    5    4   3   2   1   0
 *              HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
 *
 * @param       regno: 状态寄存器号，范围:1~3
 * @retval      状态寄存器值
 */
static uint8_t unidrv_spiflash_read_sr(mcu_spiflash_t *dev, uint8_t regno) {
    uint8_t byte, command;
    switch(regno) {
        default:
        case 1: command = FLASH_ReadStatusReg1; break; // 读状态寄存器1指令
        case 2: command = FLASH_ReadStatusReg2; break; // 读状态寄存器2指令
        case 3: command = FLASH_ReadStatusReg3; break; // 读状态寄存器3指令
    }

    dev->cs(true);
    mcu_spi_trx1(dev->spi, command);          // 发送读寄存器命令
    byte = mcu_spi_trx1(dev->spi, 0xFF);      // 读取一个字节
    dev->cs(false);    
    return byte;
}

/**
 * @brief       写25QXX状态寄存器
 * @note        寄存器说明见unidrv_spiflash_read_sr函数说明
 * @param       regno : 状态寄存器号，范围:1~3
 * @param       sr    : 要写入状态寄存器的值
 * @retval      无
 */
static void unidrv_spiflash_write_sr(mcu_spiflash_t *dev, uint8_t regno, uint8_t sr) {
    uint8_t command = 0;
    switch (regno) {
        default:
        case 1: command = FLASH_WriteStatusReg1; break; // 写状态寄存器1指令
        case 2: command = FLASH_WriteStatusReg2; break; // 写状态寄存器2指令
        case 3: command = FLASH_WriteStatusReg3; break; // 写状态寄存器3指令
    }

    dev->cs(true);
    mcu_spi_trx1(dev->spi, command);            // 发送读寄存器命令
    mcu_spi_trx1(dev->spi, sr);                 // 写入一个字节
    dev->cs(false);
}

/**
 * @brief 等待空闲
 */
static void unidrv_spiflash_wait_busy(mcu_spiflash_t *dev) {
    while((unidrv_spiflash_read_sr(dev, 1) & 0x01) == 0x01);    // 等待BUSY位清空
}

/**
 * @brief 读取 Flash 芯片 Manufact ID
 * @retval Flash 芯片 ID
 */
static ftid_t unidrv_spiflash_read_manufact_id(mcu_spiflash_t *dev) {
    uint16_t ftid;
    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_ManufactDeviceID); // 发送读 ID 命令
    ftid  = mcu_spi_trx1(dev->spi, 0);              // 写入一个字节
    ftid  = mcu_spi_trx1(dev->spi, 0);
    ftid  = mcu_spi_trx1(dev->spi, 0);
    ftid  = mcu_spi_trx1(dev->spi, 0xFF) << 8;    // 读取高8位字节
    ftid |= mcu_spi_trx1(dev->spi, 0xFF);         // 读取低8位字节
    dev->cs(false);
    return (dev->typeid = (ftid_t)ftid);
}

/**
 * @brief 读取 Flash 芯片 Jedec ID
 * @retval Flash 芯片 Jedec ID
 */
static ft_jedec_id_t unidrv_spiflash_read_jedec_id(mcu_spiflash_t *dev) {
    uint8_t cmd[4] = {FLASH_JedecDeviceID, 0xFF, 0xFF, 0xFF};
    uint8_t ack[4];
    dev->cs(true);
    mcu_spi_trx(dev->spi, cmd, ack, 4, NULL); // 发送读 ID 命令
    dev->cs(false);
    uint32_t id = (ack[1] << 16) | (ack[2] << 8) | ack[3];
    return (dev->jedecid = (ft_jedec_id_t)id);
}

/**
 * @brief       SPI在一页(0~65535)内写入少于256个字节的数据
 * @note        在指定地址开始写入最大256字节的数据
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
 * @retval      无
 */
static void unidrv_spiflash_write_page(mcu_spiflash_t *dev, const void *_buf, uint32_t addr, uint32_t datalen) {
    const uint8_t *pbuf = (const uint8_t*)_buf;
    unidrv_spiflash_write_enable(dev);  // 写使能

    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_PageProgram);  // 发送写页命令
    unidrv_spiflash_send_address(dev, addr);    // 发送地址
    for (uint32_t i = 0; i < datalen; i++) mcu_spi_trx1(dev->spi, pbuf[i]); // 循环写入    
    dev->cs(false);
    
    unidrv_spiflash_wait_busy(dev); // 等待写入结束
}

/**
 * @brief       无检验写SPI FLASH
 * @note        必须确保所写的地址范围内的数据全部为0xFF,否则在非0xFF处写入的数据将失败!
 *              具有自动换页功能
 *              在指定地址开始写入指定长度的数据,但是要确保地址不越界!
 * @param       pbuf    : 数据存储区
 * @param       addr    : 开始写入的地址(最大32bit)
 * @param       datalen : 要写入的字节数(最大65535)
 * @retval      无
 */
static void unidrv_spiflash_write_nocheck(mcu_spiflash_t *dev, const void *_buf, uint32_t addr, uint32_t datalen) {
    const uint8_t *pbuf = (const uint8_t*)_buf;
    uint32_t pageremain = dev->pagesize - (addr % dev->pagesize); // 单页剩余的字节数
    if(datalen <= pageremain) pageremain = datalen;

    while(1) {
        // 当写入字节比页内剩余地址还少的时候, 一次性写完
        // 当写入字节数比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
        unidrv_spiflash_write_page(dev, pbuf, addr, pageremain);
        datalen -= pageremain;          // 计算剩余要写入的数据长度
        if(!datalen) break;             // 写入结束了就可以直接返回

        // 准备写入下一页
        pbuf += pageremain;             // pbuf指针地址偏移,前面已经写了pageremain字节 
        addr += pageremain;             // 写地址偏移,前面已经写了pageremain字节 
        pageremain = min(dev->pagesize, datalen); // 下一页要写入的数据长度
    }
}

std_err_t unidrv_spiflash_init(mcu_spiflash_t *dev) {
    if(!dev || !dev->spi || !dev->cs || !dev->pagesize || !dev->blocksize || !dev->blockcount || !dev->blockbuf || dev->pagesize > dev->blocksize) return STD_ERR_INVALID_ARG;
    
    // 读取 Flash 型号 ID
    unidrv_spiflash_read_jedec_id(dev);
    unidrv_spiflash_read_manufact_id(dev);
    
    // 25Q256, 必须使能 4 字节地址模式
    if (dev->typeid == FTID_W25Q256 || dev->typeid == FTID_BY25Q256) {
        // 读取状态寄存器3，判断地址模式
        uint8_t temp = unidrv_spiflash_read_sr(dev, 3);
        if ((temp & 0x01) == 0) {
            // 不是 4 字节地址模式, 则要进入 4 字节地址模式
            unidrv_spiflash_write_enable(dev);      // 写使能
            temp |= 1 << 1;                         // ADP=1, 上电 4 位地址模式
            unidrv_spiflash_write_sr(dev, 3, temp); // 写 SR3
            
            dev->cs(true);
            mcu_spi_trx1(dev->spi, FLASH_Enable4ByteAddr);  // 使能4字节地址指令
            dev->cs(false);
        }
    }
    return STD_ERR_OK;
}

void unidrv_spiflash_read(mcu_spiflash_t *dev, uint8_t *pbuf, uint32_t addr, uint32_t datalen) {
    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_ReadData);     // 发送读取命令 
    unidrv_spiflash_send_address(dev, addr);    // 发送地址
    for(uint32_t i = 0; i < datalen; i++) pbuf[i] = mcu_spi_trx1(dev->spi, 0xFF); // 循环读取
    dev->cs(false);
}

void unidrv_spiflash_write(mcu_spiflash_t *dev, const void *_buf, uint32_t addr, uint32_t datalen) {
    const uint8_t *pbuf = (const uint8_t*)_buf;
    uint32_t i;
    uint32_t secpos = addr / dev->blocksize;      // 扇区号
    uint32_t secoff = addr % dev->blocksize;      // 在扇区内的偏移
    uint32_t secremain = dev->blocksize - secoff; // 扇区 secoff 后面剩余空间大小
    if(datalen <= secremain) secremain = datalen; // 要写入的数据长度 不大于 扇区 secoff 后面剩余空间大小

    while(1) {
        // 读出整个扇区的内容，以便检查要写入的地方是否已经全部擦除
        unidrv_spiflash_read(dev, dev->blockbuf, secpos * dev->blocksize, dev->blocksize);
        // 检查要写入的地方是否已经全部擦除，只要有一个字节不是 0xFF 就表示需要擦除
        for(i = 0; i < secremain; i++) {
            if(dev->blockbuf[secoff + i] != 0xFF) break; // 需要擦除, 直接退出for循环
        }

        if(i < secremain) {
            // 需要擦除，则需要擦除这个扇区（扇区数据已经读到 dev->blockbuf，可以放心擦除）
            unidrv_spiflash_erase_sector(dev, secpos);
            // 把要写入的数据复制拼接到 secoff 后面
            memcpy(dev->blockbuf + secoff, pbuf, secremain);
            // 写入拼接后的整个扇区数据
            unidrv_spiflash_write_nocheck(dev, dev->blockbuf, secpos * dev->blocksize, dev->blocksize);
        } else {
            // 要写入的地方是否已经全部擦除, 直接写入到扇区 secoff 后面即可
            unidrv_spiflash_write_nocheck(dev, pbuf, addr, secremain);
        }
        
        datalen -= secremain;           // 计算剩余要写入的数据长度
        if(!datalen) break;             // 写入结束了就可以直接返回
        
        // 准备写入下一个扇区
        secpos++;                       // 扇区地址增 1
        secoff = 0;                     // 偏移位置为 0
        pbuf += secremain;              // 指针偏移
        addr += secremain;              // 写地址偏移
        secremain = min(dev->blocksize, datalen); // 下一个扇区要写入的数据长度
    }
}

void unidrv_spiflash_erase_chip(mcu_spiflash_t *dev) {
    unidrv_spiflash_write_enable(dev);            // 写使能 
    unidrv_spiflash_wait_busy(dev);               // 等待空闲 
    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_ChipErase);      // 发送读寄存器命令  
    dev->cs(false);
    unidrv_spiflash_wait_busy(dev);               // 等待芯片擦除结束 
}

void unidrv_spiflash_erase_sector(mcu_spiflash_t *dev, uint32_t saddr) {
    saddr *= dev->blocksize;
    unidrv_spiflash_write_enable(dev);          // 写使能 
    unidrv_spiflash_wait_busy(dev);             // 等待空闲 

    dev->cs(true);
    mcu_spi_trx1(dev->spi, FLASH_SectorErase);  // 发送写页命令 
    unidrv_spiflash_send_address(dev, saddr);   // 发送地址 
    dev->cs(false);
    unidrv_spiflash_wait_busy(dev);             // 等待扇区擦除完成 
}


DSTATUS ffdisk_spiflash_init(ffdisk_t *disk) {
    if(!disk || !disk->dev || !disk->sector_count || !disk->sector_size) return RES_PARERR;
    if(disk->initialized) return RES_OK;
    mcu_spiflash_t *flash = (mcu_spiflash_t*)disk->dev;
    
    // Flash 中总共能存放多少个扇区
    uint32_t nsecmax = flash->blockcount * flash->blocksize / disk->sector_size;
    
    if( disk->sector_size < FF_MIN_SS ||                    // 一个 FAT 扇区至少要 512 字节 !!!
        disk->sector_size < flash->pagesize ||              // 扇区大小必须至少为一个页大小
        disk->sector_base + disk->sector_count > nsecmax    // 本分区所使用的扇区必须在 Flash 容量范围内
    ) return RES_PARERR;
    
    std_err_t err = unidrv_spiflash_init((mcu_spiflash_t*)disk->dev); if(err) return RES_ERROR;
    disk->initialized = true;
    return RES_OK;
}

DSTATUS ffdisk_spiflash_status(ffdisk_t *disk) {
    return RES_OK;
}

DRESULT ffdisk_spiflash_read(ffdisk_t *disk, BYTE *buff, LBA_t sector, UINT count) {
    if(!disk || !disk->dev || !buff || !count || disk->sector_base + sector + count > disk->sector_count) return RES_PARERR;
    unidrv_spiflash_read((mcu_spiflash_t*)disk->dev, buff, sector * disk->sector_size, count * disk->sector_size);
    return RES_OK;
}

DRESULT ffdisk_spiflash_write(ffdisk_t *disk, const BYTE *buff, LBA_t sector, UINT count) {
    if(!disk || !disk->dev || !buff || !count || disk->sector_base + sector + count > disk->sector_count) return RES_PARERR;    
    unidrv_spiflash_write((mcu_spiflash_t*)disk->dev, buff, sector * disk->sector_size, count * disk->sector_size);
    return RES_OK;
}

DRESULT ffdisk_spiflash_ioctl(ffdisk_t *disk, BYTE cmd, void *buff) {
    if(!disk || !disk->dev) return RES_PARERR;
    mcu_spiflash_t *flash = (mcu_spiflash_t*)disk->dev;
    
    switch (cmd) {        
        case GET_BLOCK_SIZE:
            if(buff) *(DWORD*)buff = flash->blocksize; // 擦除块大小 ?
            return RES_OK;
        
        case GET_SECTOR_SIZE:
            if(buff) *(WORD*)buff = disk->sector_size; // 若设备支持可变扇区大小（如某些Flash），需返回实际值
            return RES_OK;

        case GET_SECTOR_COUNT:
            if(buff) *(LBA_t*)buff = disk->sector_count;
            return RES_OK;

        case CTRL_SYNC:
            // 不使用文件读写缓冲区，所以无需同步操作，直接返回 OK
            return RES_OK;
            
        default: return RES_PARERR;
    }
}
